package org.lex.swing.widgets;

import java.awt.BorderLayout;
import java.awt.Component;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.util.Enumeration;
import java.util.Stack;

import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JSplitPane;
import javax.swing.JTable;
import javax.swing.JTree;
import javax.swing.event.TreeSelectionEvent;
import javax.swing.event.TreeSelectionListener;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.TableColumnModel;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeCellRenderer;
import javax.swing.tree.TreeNode;
import javax.swing.tree.TreePath;
import javax.swing.tree.TreeSelectionModel;

import org.lex.swing.ContainerFactory;
import org.lex.swing.Swings;
import org.lex.swing.table.TableHelper;
import org.lex.utils.collections.Collections;


public class NetworkInterfacePane extends JPanel {
	private JSplitPane splitPane = null;
	private JScrollPane treeScrollPane = null;
	private JScrollPane tableScrollPane = null;
	private JTree tree = null;
	private DefaultMutableTreeNode root = null;
	private JTable table = null;
	private TableModel tableModel = null;

	public static void main(String[] args) {
		Swings.setUseBoldMetalFont(false);
		Swings.show(new NetworkInterfacePane(), false);
	}

	public NetworkInterfacePane() {
		super();
		initialize();
	}

	private void initialize() {
		this.setSize(300, 200);
		this.setLayout(new BorderLayout());
		this.add(getSplitPane(), BorderLayout.CENTER);
	}

	private JSplitPane getSplitPane() {
		if (splitPane == null) {
			splitPane = ContainerFactory.createBorderlessSplitPane();
			splitPane.setDividerSize(5);
			splitPane.setResizeWeight(0.35D);
			splitPane.setLeftComponent(getTreeScrollPane());
			splitPane.setRightComponent(getTableScrollPane());
		}
		return splitPane;
	}

	private JScrollPane getTreeScrollPane() {
		if (treeScrollPane == null) {
			treeScrollPane = new JScrollPane();
			treeScrollPane.setViewportView(getTree());
		}
		return treeScrollPane;
	}

	private JScrollPane getTableScrollPane() {
		if (tableScrollPane == null) {
			tableScrollPane = new JScrollPane();
			tableScrollPane.setViewportView(getTable());
		}
		return tableScrollPane;
	}

	private JTable getTable() {
		if (table == null) {
			table = new JTable();
			// table.setAutoResizeMode( JTable.AUTO_RESIZE_OFF );
			table.setModel(this.getTableModel());
			table.setAutoCreateRowSorter(true);
			TableHelper.test(table);
			int[] widths = { 200, 265 };
			TableColumnModel model = table.getColumnModel();
			for (int i = 0; i < model.getColumnCount(); i++)
				model.getColumn(i).setPreferredWidth(widths[i]);
		}
		return table;
	}

	private TableModel getTableModel() {
		if (null == this.tableModel) {
			this.tableModel = new TableModel();
		}
		return this.tableModel;
	}

	private JTree getTree() {
		if (tree == null) {
			tree = new JTree(this.getTreeRootNode());
			tree.setCellRenderer(new TreeNodeRenderer());
			tree.getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION);
			tree.addTreeSelectionListener(new TreeSelectionListener() {
				public void valueChanged(TreeSelectionEvent e) {
					TreePath path = e.getNewLeadSelectionPath();
					if (null == path)
						return;
					DefaultMutableTreeNode node = (DefaultMutableTreeNode) path.getLastPathComponent();
					if (node == null || node == getTreeRootNode())
						return;
					NetworkInterface ni = (NetworkInterface) node.getUserObject();
					getTableModel().setNetworkInterface(ni);
				}
			});
		}
		return tree;
	}

	@SuppressWarnings("unchecked")
	private TreeNode getTreeRootNode() {
		if (null == root) {
			root = new DefaultMutableTreeNode("Root");
			this.addChild(root, this.getAllNetworkInterfaces());
			// add sub-networkinterface
			Stack<DefaultMutableTreeNode> stack = new Stack<DefaultMutableTreeNode>();
			this.addEnumerationToStack(stack, root.children());
			while (false == stack.isEmpty()) {
				DefaultMutableTreeNode node = stack.pop();
				NetworkInterface ni = (NetworkInterface) node.getUserObject();
				int counter = this.addChild(node, ni.getSubInterfaces());
				if (counter != 0)
					this.addEnumerationToStack(stack, node.children());
			}
		}
		return root;
	}

	private int addChild(DefaultMutableTreeNode parent, Enumeration<NetworkInterface> ni) {
		if (ni == null)
			return 0;
		int counter = 0;
		while (ni.hasMoreElements()) {
			parent.add(new DefaultMutableTreeNode(ni.nextElement()));
			counter++;
		}
		return counter;
	}

	private <T> int addEnumerationToStack(Stack<T> stack, Enumeration<T> e) {
		if (null == e)
			return 0;
		int counter = 0;
		while (e.hasMoreElements()) {
			stack.push(e.nextElement());
			counter++;
		}
		return counter;
	}

	private Enumeration<NetworkInterface> getAllNetworkInterfaces() {
		Enumeration<NetworkInterface> nis = null;
		try {
			nis = NetworkInterface.getNetworkInterfaces();
		} catch (SocketException e) {
			e.printStackTrace();
			nis = Collections.emptyEnumeration();
		}
		return nis;
	}

	class TreeNodeRenderer extends DefaultTreeCellRenderer {
		public Component getTreeCellRendererComponent(JTree tree, Object value, boolean sel, boolean expanded, boolean leaf, int row, boolean hasFocus) {
			String text = null;
			if (value == getTreeRootNode()) {
				text = "Network Interfaces";
			} else {
				DefaultMutableTreeNode node = (DefaultMutableTreeNode) value;
				NetworkInterface ni = (NetworkInterface) node.getUserObject();
				text = ni.getName();
			}
			return super.getTreeCellRendererComponent(tree, text, sel, expanded, leaf, row, hasFocus);
		}
	}

	private static final String[] columnNames = { "Property", "Value" };
	private static final String[] propertyNames = { "Name", "Displayed Name", "Parent", "Sub Interface", "Inet Address", "Hardware Address (MAC)",
			"Maximum Transmission Unit (MTU)", "Is Loopback", "Is Point To Point", "Is Up", "Is Virtual", "Supports Multicast" };

	class TableModel extends AbstractTableModel {
		private String none = "(none)";
		private String yes = "Yes";
		private String no = "No";
		private NetworkInterface ni = null;

		public void setNetworkInterface(NetworkInterface i) {
			this.ni = i;
			this.fireTableDataChanged();
		}

		public String getColumnName(int column) {
			return columnNames[column];
		}

		public int getColumnCount() {
			return columnNames.length;
		}

		public int getRowCount() {
			return propertyNames.length;
		}

		public Object getValueAt(int row, int column) {
			if (0 == column)
				return propertyNames[row];
			if (null == ni)
				return "";
			switch (row) {
			case 0:
				// name
				return ni.getName();
			case 1:
				// displayed name
				return ni.getDisplayName();
			case 2:
				// parent
				NetworkInterface parent = ni.getParent();
				if (null == parent)
					return none;
				else
					return parent.getDisplayName();
			case 3:
				// sub interfaces
				return this.getSubInterfaces();
			case 4:
				// Inet Addresses
				return this.getInetAddressed();
			case 5:
				// mac
				return this.getMAC();
			case 6:
				// MTU
				return this.getMTU();
			case 7:
				// is Loopback
				return this.isLoopback();
			case 8:
				// is Point to Point
				return this.isPointToPoint();
			case 9:
				// is Up
				return this.isUp();
			case 10:
				// is virtual
				return this.wrapBoolean(ni.isVirtual());
			case 11:
				// supports Multicast
				return this.supportsMulticast();
			}
			return none;
		}

		private String getSubInterfaces() {
			Enumeration<NetworkInterface> ne = ni.getSubInterfaces();
			if (false == ne.hasMoreElements())
				return none;
			StringBuilder buffer = new StringBuilder();
			while (ne.hasMoreElements())
				buffer.append(ne.nextElement().getDisplayName()).append(", ");
			if (buffer.length() > 2)
				buffer.setLength(buffer.length() - 2);
			return buffer.toString();
		}

		private String getInetAddressed() {
			Enumeration<InetAddress> ei = ni.getInetAddresses();
			if (false == ei.hasMoreElements())
				return none;
			StringBuilder buffer = new StringBuilder();
			while (ei.hasMoreElements()) {
				InetAddress addr = ei.nextElement();
				buffer.append(addr.getHostName()).append("/").append(addr.getHostAddress()).append(", ");
			}
			if (buffer.length() > 2)
				buffer.setLength(buffer.length() - 2);
			return buffer.toString();
		}

		private String getMAC() {
			try {
				byte[] mac = ni.getHardwareAddress();
				if (null == mac)
					return none;
				StringBuilder buffer = new StringBuilder(mac.length * 3);
				for (int i = 0; i < mac.length; i++) {
					int value = mac[i] < 0 ? 256 + mac[i] : mac[i];
					String hex = Integer.toHexString(value).toUpperCase();
					if (1 == hex.length())
						buffer.append("0");
					buffer.append(hex).append("-");
				}
				if (buffer.length() > 1)
					buffer.setLength(buffer.length() - 1);
				return buffer.toString();
			} catch (SocketException e) {
				e.printStackTrace();
			}
			return none;
		}

		private String getMTU() {
			System.out.println("Native MTU");
			try {
				return Integer.toString(ni.getMTU());
			} catch (SocketException e) {
				e.printStackTrace();
			}
			return none;
		}

		private String isLoopback() {
			try {
				return this.wrapBoolean(ni.isLoopback());
			} catch (SocketException e) {
				e.printStackTrace();
			}
			return none;
		}

		private String isPointToPoint() {
			try {
				return this.wrapBoolean(ni.isPointToPoint());
			} catch (SocketException e) {
				e.printStackTrace();
			}
			return none;
		}

		private String isUp() {
			try {
				return this.wrapBoolean(ni.isUp());
			} catch (SocketException e) {
				e.printStackTrace();
			}
			return none;
		}

		private String supportsMulticast() {
			try {
				return this.wrapBoolean(ni.supportsMulticast());
			} catch (SocketException e) {
				e.printStackTrace();
			}
			return none;
		}

		private String wrapBoolean(boolean value) {
			return value ? yes : no;
		}
	}
}
